using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using GoldDigger.Common;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using TPL.Xna.Common;

namespace GoldDigger.Sprites
{
    public enum HookState
    {
        Root,
        Lauch,
        Pull,
    }

    internal class HookSprite : ImageSprite
    {
        /// <summary>
        /// State of hook, 3 possible values
        /// - Root: the hook is rotated for chosing direction
        /// - Lauch: after user tapping to the screen to lauch the hook
        /// - Pull: collide with either object or screen border cause this state, then the hook is pull back
        /// the speed is dermine base on the collided object.
        /// </summary>
        public HookState HookState { get; set; }

        /// <summary>
        /// Speed of lauching hook, this can be changed by buying Strength.
        /// </summary>
        public float LaunchingSpeed { get; set; }

        /// <summary>
        /// Speed of pulling back hook, this depends on the object and the strength.
        /// </summary>
        private float _pullingSpeed;

        /// <summary>
        /// Keep current direction of hook, ranging from -80 to 80 degree.
        /// </summary>
        private Vector2 _direction = Vector2.Zero;

        /// <summary>
        /// Position of hook image.
        /// </summary>
        private Vector2 _root;

        private readonly ISceneManager _sceneManager;

        /// <summary>
        /// A simple hack for drawing line and rect.
        /// </summary>
        private readonly Texture2D _rectImage;

        /// <summary>
        /// Keep a rectagle for drawing line from pully to hook, simulating rope.
        /// </summary>
        private Rectangle _ropeRect;

        /// <summary>
        /// Rotate origin of rope rect
        /// </summary>
        private readonly Vector2 _ropeRectOrigin = new Vector2(0, 0);

        /// <summary>
        /// Currently pulling item.
        /// </summary>
        private ItemSprite _pullingItem;

        /// <summary>
        /// Height of hook => for calculating hooked item's position.
        /// </summary>
        private float _hookHeight;

        /// <summary>
        /// Animating the rotation.
        /// </summary>
        private FloatAnimator _rotateAnimator;

        /// <summary>
        /// Create with hook image and rotate origin together with rect image for drawing primitives.
        /// </summary>
        /// <param name="sceneManager"></param>
        /// <param name="origin"></param>
        /// <param name="rectImage"></param>
        /// <param name="image"></param>
        public HookSprite(ISceneManager sceneManager, Texture2D image, Vector2 origin, Texture2D rectImage)
            : base(image)
        {
            Origin = origin;

            _sceneManager = sceneManager;
            _rectImage = rectImage;

            // Default lauching and pulling speed
            // Both speeds can be changed by applying Strength item
            // Pulling speed is also affected the item catched by hook
            LaunchingSpeed = _pullingSpeed = 8f;

        }
        
        private SpriteArea _area;
        public override SpriteArea SpriteArea
        {
            get
            {
                if (_area != null) return _area;
                var points = new List<Vector2>
                                 {
                                     new Vector2(2, Image.Height-8),
                                     new Vector2(0, Image.Height-2),
                                     new Vector2(8, Image.Height-6),
                                     new Vector2(14, Image.Height-6),
                                     new Vector2(20, Image.Height-6),
                                     new Vector2(26, Image.Height-3),
                                     new Vector2(24, Image.Height-8),
                                 };

                _area = new MeshSpriteArea(Screen.ScreenManager.GraphicsDevice, points.Select(s=>s*Scale));
                _area.Shape.Origin = new Vector2((float)Image.Width/2, 0) * Scale;
                return _area;
            }
        }

        /// <summary>
        /// Setup some initial value values.
        /// </summary>
        public override void LoadContent()
        {
            // Store root location
            _root = Position;
            // Set collision area position
            SpriteArea.Shape.Position = Position - Origin * Scale;

            // For rotating
            AddAnimator(_rotateAnimator = new FloatAnimator(s => s.Angle)
                                              {
                                                  // Setup valid values for rotation
                                                  // Cannot be changed, [-80,80] degree cover the most play so that hook can reach
                                                  Min = MathHelper.ToRadians(-80),
                                                  Max = MathHelper.ToRadians(80),
                                                  //Min = (float)(0 * Math.PI / 180),
                                                  //Max = (float)(0 * Math.PI / 180),

                                                  // Can be change by apply PyllyRotation item
                                                  Step = MathHelper.ToRadians(3f),
                                              });
            // Uncomment this to allow the drawing of collision area
            _rotateAnimator.AttachTo(SpriteArea.Shape);

            // Set hook height for later use
            _hookHeight = Image.Height * Scale;

            // Rope rect is sized 2 in width and root at the pulley,
            // Height is the length of rope, will be changed when hook is lauched or pulled
            _ropeRect = new Rectangle((int)_root.X, (int)_root.Y, 2, 0);

            base.LoadContent();
        }

        /// <summary>
        /// Calculate sprites attributes.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="clientBounds"></param>
        protected override void InternalUpdate(GameTime gameTime, Rectangle clientBounds)
        {
            switch (HookState)
            {
                case HookState.Root:
                    // Rotate hook and clear direction
                    _direction = Vector2.Zero;
                    // Enable hook swing
                    _rotateAnimator.Enable = true;
                    break;
                case HookState.Lauch:
                    // Calculcate direction once at first update
                    // consequence updates will use the same direction
                    if (_direction == Vector2.Zero)
                    {
                        _direction.X = -(float)Math.Sin(Angle);
                        _direction.Y = (float)Math.Cos(Angle);
                        _direction.Normalize();
                    }
                    // Update launchinh position
                    UpdateHook(LaunchingSpeed);
                    Sprite item;
                    if ((item = GetCollisionItem()) != null || IsOutOfScreen())
                    {
                        // if collision is detected => change to Pull mode
                        HookState = HookState.Pull;
                        // Reverse direction
                        _direction = -_direction;

                        if (item != null)
                        {
                            Debug.Assert(item is ItemSprite);
                            _pullingItem = (ItemSprite)item;
                            // Determine pulling speed base on weight value
                            _pullingSpeed = LaunchingSpeed / _pullingItem.Weight;
                            // signal the item that it is being collected
                            _pullingItem.MarkAsBeingCollected();

                            // Change displayed angle of sprite
                            _pullingItem.Angle = _pullingItem.SpriteArea.Shape.Angle = Angle;
                            var distance = _direction * _hookHeight;
                            _pullingItem.Position = Position - distance;
                            // Set collision area position => TODO MOVE to Sprite class 
                            _pullingItem.SpriteArea.Shape.Position = _pullingItem.Position - _pullingItem.Origin * _pullingItem.Scale;
                        }
                        else
                        {
                            _pullingSpeed = LaunchingSpeed;
                        }
                    }

                    // Disable hook swing
                    _rotateAnimator.Enable = false;
                    break;
                case HookState.Pull:
                    // Update pulling position
                    UpdateHook(_pullingSpeed);
                    // Over root position => make the position at root
                    if (Position.Y <= _root.Y)
                    {
                        Position = _root;
                        SpriteArea.Shape.Position = Position - Origin * Scale;
                        HookState = HookState.Root;
                        if (_pullingItem != null)
                            _sceneManager.CollectItem(_pullingItem);
                        _pullingItem = null;
                    }
                    // Disable hook swing
                    _rotateAnimator.Enable = false;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            base.InternalUpdate(gameTime, clientBounds);
        }

        /// <summary>
        /// Update hook according to its speed.
        /// </summary>
        /// <param name="speed"></param>
        private void UpdateHook(float speed)
        {
            var v = _direction * speed;
            // move according to the vector using direction and length
            Move(v);
            // Move item if any
            if (_pullingItem != null) _pullingItem.Move(v);
            // Reposition rope rect
            _ropeRect.Height = (int)(Position - _root).Length();
        }

        /// <summary>
        /// Draw the rotate sprite together with its rope and 
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="spriteBatch"></param>
        protected override void InternalDraw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            // Draw rope
            spriteBatch.Draw(_rectImage, _ropeRect, null, Color.White, Angle, _ropeRectOrigin, SpriteEffects.None, ZIndex);
            // Draw the hook);
            base.InternalDraw(gameTime, spriteBatch);
        }
    }
}
